package pprg.core;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Runnable for calculation an Game of Life iteration.
 * 
 * @author Daniel Heily, Michael Hinterleitner.
 *
 */
public class GameOfLifeWorker implements Runnable {

	World world;
	int x;
	int y;
	int numRows;
	int numberIterations;
	CyclicBarrier barrier;
	private int count;
	private int[][] sumArray;

	/**
	 * The Threads knows the x and y start position (left up) of the rows on
	 * which it has to work. it also knows the amount of rows. The World it gets
	 * goes from row y-1 to (y+numRows)+1. Because for the calculation of row y
	 * the worker needs info about row y-1. (same for row y+numRows).
	 * 
	 * @param x
	 *            collum index of first row.
	 * @param y
	 *            row index of first row
	 * @param numRows
	 *            number of rows on which thread has to work
	 * @param world
	 *            part of whole world (from y-1 to y+numRows+1)
	 * @param numberIterations
	 *            number of iterations thread has to work
	 * @param barrier
	 *            the {@link CyclicBarrier} for synchronizing with other
	 *            Threads.
	 */
	//

	public GameOfLifeWorker(int x, int y, int numRows, World world,
			int numberIterations, CyclicBarrier barrier) {
		this.world = world;
		this.x = x;
		this.y = y;
		this.numRows = numRows;
		this.numberIterations = numberIterations;
		this.barrier = barrier;
		sumArray = new int[numRows][world.getWidth()];
	}

	@Override
	public void run() {
		while (count < numberIterations) {
			try {
				calcSums();
				updateWorld();
				barrier.await();
			} catch (InterruptedException | BrokenBarrierException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Calculate the sums of living neighbors for each cell.
	 */
	private void calcSums() {
		for (int x = 0; x < world.getWidth(); x++) {
			for (int y = 0; y < numRows; y++) {
				sumArray[y][x] = sum(x, y + 1);
			}
		}
	}

	private int sum(int x, int y) {
		int sum = 0;
		for (Direction dir : Direction.values()) {
			if (world.isAlive(x + dir.dx, y + dir.dy)) {
				sum++;
			}
		}
		return sum;
	}

	/**
	 * update world for next calculation according to play-rules.
	 */
	private void updateWorld() {
		for (int x = 0; x < world.getWidth(); x++) {
			for (int y = 0; y < numRows; y++) {
				if (world.isAlive(x, y + 1)) {
					if (sumArray[y][x] < 2 || sumArray[y][x] > 3) {
						world.setAlive(false, x, y + 1);
					}
				} else if (sumArray[y][x] == 3) {
					world.setAlive(true, x, y + 1);
				}
			}
		}
	}

	public boolean[][] getWorldBoard() {
		boolean[][] target = new boolean[numRows][world.getWidth()];
		boolean[][] src = world.getBoard();
		for (int y = 1; y < numRows + 1; y++) {
			System.arraycopy(src[y], 0, target[y - 1], 0, world.getWidth());
		}
		return target;
	}

	public World getWorld() {
		return world;
	}

	public void incrementIteration() {
		count++;
	}

	public int getIterationCount() {
		return count;
	}

	public int[][] getSumArray() {
		return sumArray;
	}

	public int getX() {
		return x;
	}

	public int getY() {
		return y;
	}

	public void setWorld(World sw) {
		this.world = sw;
	}

	public int getNumRows() {
		return numRows;
	}

	public void stop() {
		count = numberIterations;
	}

}
